/**
 * LinkedLST, Kellen Donohue
 * May 8, 2011
 * DataGet.java - Coordinates all operations to get data from the backend
 */

package edu.washington.cs.labspecimentransport.data;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.conn.ConnectTimeoutException;
import org.json.JSONArray;

import edu.washington.cs.labspecimentransport.dataobjects.*;

/**
 * 
 * @author Kellen
 * 
 */
public class DataGet<E> {
	/**
	 * URL that this data get hits
	 */
	private String targetURL;

	/**
	 * Store for data retrieved from the backend
	 */
	private List<E> retreivedValues;

	/**
	 * Whether the DataGet has been executed once
	 */
	private boolean executed;

	/**
	 * How long after this DataGet has been received it has been cached for
	 */
	private long timeout;

	/**
	 * The time this DataGet was last retrieved
	 */
	private long lastRetreived;

	private static final long FIVE_MIN = 60 * 5 * 1000;
	private static final long ONE_HOUR = 60 * 60 * 1000;
	@SuppressWarnings("unused")
	private static final long ONE_DAY = ONE_HOUR * 24;
	public static final long NEVER_TIMEOUT = -1;

	/**
	 * Listing of possible results from a DataGet execution
	 * 
	 * @author Kellen Class to retrieve data from a data store, either the
	 *         backend or the local cache
	 */
	public enum GetResult {
		CacheHit, ReceivedOK, NoResults, NoConnection, ServerSideError
	};

	/**
	 * Perform the data get operation, contacting the cache or database backend
	 * 
	 * @return The result of performing the data get
	 */
	@SuppressWarnings("unchecked")
	public GetResult execute() throws ConnectTimeoutException {
		// Still valid, don't do anything
		if (this.executed
				&& (System.currentTimeMillis() - this.lastRetreived < this.timeout || this.timeout == NEVER_TIMEOUT)) {
			return GetResult.CacheHit;
		}

		this.executed = true;
		try {
			String result = HttpRequest.getInstance().sendGetRequest(
					this.targetURL);
			JSONArray json = new JSONArray(result);
			List<E> results = new ArrayList<E>();
			for (int i = 0; i < json.length(); i++) {
				JavaObjectPopulator obj = new JavaObjectPopulator(
						json.getJSONObject(i));
				results.add((E) obj.getObject());
			}
			this.retreivedValues = results;
			this.lastRetreived = System.currentTimeMillis();
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			return GetResult.NoConnection;
		} catch (IOException e) {
			e.printStackTrace();
			return GetResult.NoConnection;
		} catch (Exception e) {
			e.printStackTrace();
			return GetResult.NoConnection;
		}
		return GetResult.ReceivedOK;
	}

	/**
	 * Get the value of a data execute operation
	 * 
	 * @return The value retrieved from the data store
	 */
	public List<E> getValues() {
		if (!this.executed) {
			return null;
		}
		return this.retreivedValues;
	}

	/**
	 * These may be created through some sort of factory, for right now don't
	 * make any until the details have been established
	 */
	private DataGet() {
		this.executed = false;
		this.retreivedValues = new ArrayList<E>();
		// Default value
		this.timeout = ONE_HOUR;
	}

	public static DataGet<Item> locationItemGetter(int locationId) {
		DataGet<Item> dg = new DataGet<Item>();
		dg.targetURL = "/locations/outgoing/" + locationId + ".json";
		dg.timeout = 0;
		return dg;
	}

	public static DataGet<StopInstance> courierStopGetter(int courierId) {
		DataGet<StopInstance> dg = new DataGet<StopInstance>();
		dg.targetURL = "/couriers/current_stops/" + courierId + "/";
		dg.timeout = ONE_HOUR;
		return dg;
	}

	public static DataGet<Item> itemByBarcodeGetter(String barcode) {
		DataGet<Item> dg = new DataGet<Item>();
		dg.targetURL = "/items/by_barcode/" + barcode + "/";
		dg.timeout = ONE_HOUR;
		return dg;
	}

	public static DataGet<Identifier> barcodesOfItem(int itemId) {
		DataGet<Identifier> dg = new DataGet<Identifier>();
		dg.targetURL = "/items/barcode/" + itemId + ".json";
		dg.timeout = ONE_HOUR;
		return dg;
	}

	public static DataGet<Comment> commentsOfItem(int itemId) {
		DataGet<Comment> dg = new DataGet<Comment>();
		dg.targetURL = "/items/comments/" + itemId + "/";
		dg.timeout = FIVE_MIN;
		return dg;
	}

	public static DataGet<Item> getItemById(int id) {
		DataGet<Item> dg = new DataGet<Item>();
		dg.targetURL = "/items/" + id + ".json";
		dg.timeout = 0;
		return dg;
	}

	public static DataGet<ItemType> itemTypeGetter(int itemTypeId) {
		DataGet<ItemType> dg = new DataGet<ItemType>();
		dg.targetURL = "/item_types/" + itemTypeId + ".json";
		// ItemTypes are immutable?
		dg.timeout = 1000;
		return dg;
	}

	public static DataGet<ItemAttribute> attributesByItemId(int id) {
		DataGet<ItemAttribute> dg = new DataGet<ItemAttribute>();
		dg.targetURL = "/items/item_attributes/" + id + ".json";
		dg.timeout = ONE_HOUR;
		return dg;
	}

	public static DataGet<ItemAttribute> itemCapColor(int itemId) {
		DataGet<ItemAttribute> dg = new DataGet<ItemAttribute>();
		dg.targetURL = "/items/cap_color/" + itemId + ".json";
		dg.timeout = ONE_HOUR;
		return dg;
	}

	public static DataGet<Item> bagContainedItemsFromBarcode(String barcode) {
		DataGet<Item> dg = new DataGet<Item>();
		dg.targetURL = "/items/bag_contents_by_barcode/" + barcode + ".json";
		dg.timeout = ONE_HOUR;
		return dg;
	}

	public static DataGet<Item> bagContainedItemsFromId(int itemId) {
		DataGet<Item> dg = new DataGet<Item>();
		dg.targetURL = "/items/bag_contents/" + itemId + ".json";
		dg.timeout = ONE_HOUR;
		return dg;
	}

	public static DataGet<Location> locationGetter(Integer location_id) {
		DataGet<Location> dg = new DataGet<Location>();
		dg.targetURL = "locations/" + location_id + ".json";
		dg.timeout = ONE_HOUR;
		return dg;
	}

	public static DataGet<User> userById(int id) {
		DataGet<User> dg = new DataGet<User>();
		dg.targetURL = "users/" + id + ".json";
		dg.timeout = ONE_HOUR;
		return dg;
	}

	/**
	 * The cache key for this data get
	 * 
	 * @return
	 */
	public String getCacheKey() {
		return this.targetURL;
	}

	/**
	 * The timeout for cache entries gotten from this object
	 * 
	 * @return
	 */
	public long getTimeOut() {
		return this.timeout;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (executed ? 1231 : 1237);
		result = prime * result
				+ ((retreivedValues == null) ? 0 : retreivedValues.hashCode());
		result = prime * result
				+ ((targetURL == null) ? 0 : targetURL.hashCode());
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		@SuppressWarnings("rawtypes")
		DataGet other = (DataGet) obj;
		if (executed != other.executed)
			return false;
		if (retreivedValues == null) {
			if (other.retreivedValues != null)
				return false;
		} else if (!retreivedValues.equals(other.retreivedValues))
			return false;
		if (targetURL == null) {
			if (other.targetURL != null)
				return false;
		} else if (!targetURL.equals(other.targetURL))
			return false;
		return true;
	}

	public static DataGet<Courier> loggedInCourier() {
		DataGet<Courier> dg = new DataGet<Courier>();
		dg.targetURL = "users/logged_in/" + 1 + ".json";
		dg.timeout = ONE_HOUR;
		return dg;
	}

	public static DataGet<ChecklistItem> beforeChecklistItems() {
		DataGet<ChecklistItem> dg = new DataGet<ChecklistItem>();
		dg.targetURL = "checklists/get_before/1";
		dg.timeout = ONE_HOUR;
		return dg;
	}

	public static DataGet<ChecklistItem> afterChecklistItems() {
		DataGet<ChecklistItem> dg = new DataGet<ChecklistItem>();
		dg.targetURL = "checklists/get_after/1";
		dg.timeout = ONE_HOUR;
		return dg;
	}
}
